﻿/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

using System.Collections.Generic;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Daidalos.Patterns;
using Daidalos.Utilities;
using System.Reflection;

namespace Daidalos.Graphics
{

    /// <summary>
    ///     The singleton rendering device is the link between the program
    ///     and the graphics driver. Thus all invocations regarding rendering
    ///     are performed by this device.
    /// </summary>
    public sealed class RenderDevice : SingletonManager<RenderDevice>
    {

		public bool loading = true;
        Daidalos.Utilities.Timer timer = new Daidalos.Utilities.Timer();

		#region Attributes
        /// <summary>
        ///     The instance of the Direct3D device.
        /// </summary>
        private Microsoft.DirectX.Direct3D.Device device = null;

        /// <summary>
        ///     The target window to render graphics to.
        /// </summary>
        private RenderWindow window = new RenderWindow();

        /// <summary>
        ///     The default texturing filter used fo mini-
        ///     and magnification of textures.
        /// </summary>
        private Microsoft.DirectX.Direct3D.TextureFilter defaultTextureFilter = TextureFilter.Linear;

        /// <summary>
        ///     A flag indicating that the geometry should be displayed as
        ///     a wireframe.
        /// </summary>
        private bool wireframe = false;

        /// <summary>
        ///     A flag indicating that the textures should be rendered
        ///     using dithering.
        /// </summary>
        private bool dithering = false;
        #endregion

        #region Properties
		/// <summary>
		///		Retrieve the width of the renderwindow.
		/// </summary>
		/// <remarks>
		///		This implementation is a bit ugly, but the font
		///		shader needs the window dimensions to position
		///		the font correctly.
		/// </remarks>
		public int ScreenWidth
		{
			get
			{
				return (window == null) ? 0 : window.Width;
			}
		}

		/// <summary>
		///		Retrieve the height of the renderwindow.
		/// </summary>
		/// <remarks>
		///		This implementation is a bit ugly, but the font
		///		shader needs the window dimensions to position
		///		the font correctly.
		/// </remarks>
		public int ScreenHeight
		{
			get
			{
				return (window == null) ? 0 : window.Height;
			}
		}

        /// <summary>
        ///     Implements accessors to the default texture filter.
        /// </summary>
        public Microsoft.DirectX.Direct3D.TextureFilter DefaultTextureFilter
        {
            get
            {
                return defaultTextureFilter;
            }
            set
            {
                defaultTextureFilter = value;
            }
        }

        /// <summary>
        ///     The accessor for the Direct3D device.
        /// </summary>
        public Microsoft.DirectX.Direct3D.Device Device
        {
            get
            {
                return device;
            }
            set
            {
                device = value;
            }
        }

        /// <summary>
        ///     The accessor for the target window.
        /// </summary>
        public RenderWindow Window
        {
            get
            {
                return window;
            }
            set
            {
                window = value;
            }
        }

        /// <summary>
        ///     The accessor to the wireframe flag.
        /// </summary>
        public bool Wireframe
        {
            get
            {
                return wireframe;
            }
            set
            {
                if (device == null)
                {
                    LogManager.Instance.Log("Failed to switch wireframe flag. Device not initialized!");
                }
                else
                {
                    wireframe = value;
                    LogManager.Instance.Log((wireframe ? "A" : "Dea") + "ctivating wireframe rendering.");
                    Device.RenderState.FillMode = wireframe ? FillMode.WireFrame : FillMode.Solid;
                }
            }
        }

        /// <summary>
        ///     The accessor to the dithering flag.
        /// </summary>
        public bool Dithering
        {
            get
            {
                return dithering;
            }
            set
            {
                if (device == null)
                {
                    LogManager.Instance.Log("Failed to switch dithering. Device not initialized!");
                }
                else
                {
                    dithering = value;
                    LogManager.Instance.Log((dithering ? "A" : "Dea") + "ctivating dithering.");
                    Device.RenderState.DitherEnable = value;
                }
            }
        }
        #endregion

        #region Methods
		/// <summary>
		///		Render the loading screen.
		/// </summary>
		private void DisplayLoadingScreen()
		{
			// Clear the screen.
			Device.Clear(
				ClearFlags.Target,
				System.Drawing.Color.FromArgb(14540253),
				1.0f,
				0);
			
			// Fetch the texture.
			Graphics.Texture loadTexture = Daidalos.Utilities.ResourceManager.Instance.GetTextureByName("loading.png");

			// Build the software buffer.
			CustomVertex.TransformedTextured[] v = new CustomVertex.TransformedTextured[6];

			// Determine top left corner
			int top = (ScreenHeight - 500) / 2;
			int left = (ScreenWidth - 500) / 2;

			v[0].X = left; v[0].Y = top; v[0].Z = 1; v[0].Rhw = 1; v[0].Tu = 0.0f; v[0].Tv = 0.0f;
			v[1].X = left + 500; v[1].Y = top + 500; v[1].Z = 1; v[1].Rhw = 1; v[1].Tu = 1.0f; v[1].Tv = 1.0f;
			v[2].X = left; v[2].Y = top + 500; v[2].Z = 1; v[2].Rhw = 1; v[2].Tu = 0.0f; v[2].Tv = 1.0f;
			v[4].X = left + 500; v[4].Y = top; v[4].Z = 1; v[4].Rhw = 1; v[4].Tu = 1.0f; v[4].Tv = 0.0f;
			v[3] = v[0];
			v[5] = v[1];

			// Create vertex buffer.
			VertexBuffer b = new VertexBuffer(
				typeof(CustomVertex.TransformedTextured),
				6,
				device,
				Usage.WriteOnly,
				CustomVertex.TransformedTextured.Format,
				Pool.Default);
			
			GraphicsStream s = b.Lock(0, 0, 0);
			s.Write(v);
			b.Unlock();

			// Draw.
			loadTexture.Bind(0);
			Device.VertexFormat = CustomVertex.TransformedTextured.Format;
			Device.SetStreamSource(0, b, 0);
			Device.DrawPrimitives(PrimitiveType.TriangleList, 0, 2);
			b.Dispose();
		}

        /// <summary>
        ///     This method tries to create the device for rendering using
        ///     default values for the window.
        /// </summary>
        public void CreateDevice()
        {
            try
            {
                CreateDevice(800, 600, false);
            }
            catch(Exception)
            {
                throw new Exception("Creation of the window render-device failed!");
            }
        }

        /// <summary>
        ///     This method tries to create the device for rendering using
        ///     the supplied values.
        /// </summary>
        /// <param name="width">
        ///     The desired width of the viewport.
        /// </param>
        /// <param name="height">
        ///     The desired height of the viewport.
        /// </param>
        /// <param name="fullscreen">
        ///     Indicate if the device should use the fullscreen mode.
        /// </param>
        /// <param name="title">
        ///     The desired title of the window.
        /// </param>
        public void CreateDevice(int width, int height, bool fullscreen)
        {
			// Fetch the local assembly.
			Assembly localAssembly = Assembly.GetCallingAssembly();
			object[] assemblyAttributes = localAssembly.GetCustomAttributes(true);
			string assemblyName = null;
			string assemblyVersion = null;
			foreach (object attribute in assemblyAttributes)
			{
				if (attribute is AssemblyTitleAttribute)
				{
					assemblyName = ((AssemblyTitleAttribute)attribute).Title;
				}
				if (attribute is AssemblyVersionAttribute)
				{
					assemblyVersion = ((AssemblyVersionAttribute)attribute).Version;
				}
			}

			// Create window title.
			string title = assemblyName + " " + assemblyVersion;

			// Start the application timer.
			timer.Start();

            // Create the window.
            LogManager.Instance.Log("Creating Device with " + width + "x" + height + " pixels.");
            Window.CreateWindow(width, height, fullscreen, title);

			// Register search paths.
			Utilities.ResourceManager.Instance.AddTextureSearchPath(@"textures");

			// Register shaders by creating them.
			new Shaders.FontShaderSet();
		}
		
		/// <summary>
		///     This method shuts down the rendering device.
		/// </summary>
		public void Shutdown()
        {
            Device.Dispose();
        }

        // TODO: comment!
        public void Render()
        {
			// Initialize the pass.
            bool beginSceneCalled = false;

            // Check if the device is valid.
            if(Device == null)
            {
                throw new Exception("Render-device invalid!");
            }

            // Clear the screen.
            Device.Clear(ClearFlags.Target, System.Drawing.Color.Black, 1.0f, 0);

            try
            {
				// Prepare the scene and reset the matrix.
				Device.BeginScene();
				beginSceneCalled = true;
	
				// Display the loading screen?
				if (loading)
				{
					DisplayLoadingScreen();
				}
				else
				{
                }
			}
            finally
            {
                // End the scene.
                if (beginSceneCalled)
                {
                    Device.EndScene();
                }
            }

            // Flip the backbuffer and the frontbuffer.
            Device.Present();
        }
        #endregion

    }

}
